﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Linq;
using System.Net;
using ActiveDirectoryService.Library.Configuration;
using ActiveDirectoryService.Library.ExtensionMethods;
using LinqToLdap;
using LinqToLdap.Mapping;

namespace ActiveDirectoryService.Library.Services
{
    public sealed class LightWeightDirectoryAccessService<T> : ILightWeightDirectoryAccessService<T> where T : DirectoryObjectBase
    {
        private readonly IActiveDirectoryConfiguration m_Configuration;
        private LdapConnectionFactory m_Factory;

        #region Constructor

        public LightWeightDirectoryAccessService() : this(new ActiveDirectoryConfiguration()) { }
        public LightWeightDirectoryAccessService(IActiveDirectoryConfiguration configuration)
        {
            m_Configuration = configuration;
            InitiateLdapConnectionFactory();
        }

        #endregion

        #region Public Methods

        public IList<T> CreateQuery(char separator = '+')
        {
            return CreateQuery(null, separator);
        }

        public IList<T> CreateQuery(string customFilter, char separator = '+')
        {
            return CreateQuery(default(T), customFilter, separator);
        }

        public IList<T> CreateQuery(string organisationUnit, string customFilter, char separator = '+')
        {
            return CreateQuery(organisationUnit, default(T), customFilter, separator);
        }

        public IList<T> CreateQuery(T userFilter, string customFilter, char separator = '+')
        {
            return CreateQuery(m_Configuration.ActiveDirectoryPath, userFilter, customFilter, separator);
        }

        public IList<T> CreateQuery(string organisationUnit, T filter, string customFilter, char separator = '+')
        {
            using (var context = new DirectoryContext(m_Factory.GetConnection(), disposeOfConnection: true))
            {
                var query = context.Query<T>(SearchScope.Subtree, organisationUnit);

                if (!String.IsNullOrEmpty(customFilter))
                {
                    query = query.FilterWith(customFilter);
                }

                if (filter != null)
                {
                    var predicate = filter.BuildExpressionTree(separator);

                    if (predicate != null)
                    {
                        query = query.Where(predicate);
                    }
                }
                return query.ToList();
            }
        }

        #endregion

        #region Private Methods

        private void InitiateLdapConnectionFactory()
        {
            m_Factory = new LdapConnectionFactory(m_Configuration.ActiveDirectoryServer);

            if (!String.IsNullOrEmpty(m_Configuration.UserName) && !String.IsNullOrEmpty(m_Configuration.Password))
            {
                var credentials = !String.IsNullOrEmpty(m_Configuration.ActiveDirectoryDomain)
                              ? new NetworkCredential(m_Configuration.UserName, m_Configuration.Password, m_Configuration.ActiveDirectoryDomain)
                              : new NetworkCredential(m_Configuration.UserName, m_Configuration.Password);

                m_Factory.AuthenticateBy(AuthType.Basic)
                         .AuthenticateAs(credentials)
                         .ProtocolVersion(3)
                         .UsePort(389);
            }
            else
            {
                m_Factory.AuthenticateBy(AuthType.Negotiate)
                         .AuthenticateAs(CredentialCache.DefaultNetworkCredentials)
                         .UsePort(389);
            }
        }

        #endregion

    }
}